using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Common {
	public class Tag : IEnumerable<string> {
		/// <summary>
		/// Initialize a empty tag.
		/// </summary>
		public Tag() {

		}
		/// <summary>
		/// Initialize a tag with given tags.
		/// </summary>
		/// <param name="tags">Tags.</param>
		public Tag(params string[] tags) {
			foreach (string tag in tags) {
				this[tag] = true;
			}
		}

		readonly HashSet<string> tags = new HashSet<string>();

		/// <summary>
		/// Check or set the tag.
		/// </summary>
		/// <value>Whether this has the tag.</value>
		public bool this[string tag] {
			get => tags.Contains(tag);
			set {
				if (value) {
					tags.Add(tag);
				} else {
					tags.Remove(tag);
				}
			}
		}

		/// <summary>
		/// Tag count.
		/// </summary>
		public int Count => tags.Count;

		public IEnumerator<string> GetEnumerator() {
			return ((IEnumerable<string>)tags).GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator() {
			return ((IEnumerable)tags).GetEnumerator();
		}

		enum MatchMode {
			Or,
			And,
			Not
		}

		/// <summary>
		/// Check if the tag matches the pattern.
		/// </summary>
		/// <param name="pattern">The pattern for matching</param>
		/// <returns>Whether the tag matches the pattern.</returns>
		public bool IsMatch(string pattern) {
#if STANDARD
			string[] conditions = pattern.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
#endif
#if GODOT
			string[] conditions = pattern.Split(new char[]{';'},StringSplitOptions.RemoveEmptyEntries);
#endif
			List<(MatchMode, string)> matches = new List<(MatchMode, string)>();

			// Analyze conditions.
			foreach (string condition in conditions) {
				// Check operator
				string op = condition.Substring(0, 1);
#if STANDARD
				MatchMode mode = op switch {
					"+" => MatchMode.And,
					"-" => MatchMode.Not,
					_ => MatchMode.Or
				};
#endif
#if GODOT
				MatchMode mode = MatchMode.Or;
				switch (op) {
					case "+":
						mode = MatchMode.And;
						break;
					case "-":
						mode = MatchMode.Not;
						break;
				}
#endif

				// Check tag name.
				string tag;
				if (mode == MatchMode.Or) {
					tag = condition;
				} else {
					tag = condition.Substring(1, condition.Length - 1);
				}

				matches.Add((mode, tag));
			}

			// Condition check.
			bool orSatisfied = false;
			foreach ((MatchMode, string) match in matches) {
				if (match.Item1 == MatchMode.Or) {
					if (orSatisfied) {
						continue;
					}
					if (!this[match.Item2]) {
						return false;
					}
					orSatisfied = true;
				} else if (match.Item1 == MatchMode.And) {
					if (!this[match.Item2]) {
						return false;
					}
				} else if (match.Item1 == MatchMode.Not) {
					if (this[match.Item2]) {
						return false;
					}
				}
			}
			return true;
		}
	}
}